/*
 * @(#)MimirScheme.java	1.0 Apr 17, 2008
 *
 *	The MIT License
 *
 *	Copyright (c) 2008 Malachi de AElfweald <malachid@gmail.com>
 *
 *	Permission is hereby granted, free of charge, to any person obtaining a copy
 *	of this software and associated documentation files (the "Software"), to deal
 *	in the Software without restriction, including without limitation the rights
 *	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *	copies of the Software, and to permit persons to whom the Software is
 *	furnished to do so, subject to the following conditions:
 *
 *	The above copyright notice and this permission notice shall be included in
 *	all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *	THE SOFTWARE.
 */
package org.eoti.mimir;

import org.eoti.io.file.FileUtil;
import org.eoti.io.file.filter.ExtensionFileFilter;
import org.eoti.io.stream.URIInputStream;
import org.eoti.spec.mimirdb.v1.DBMapping;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;

/**
 * This class represents a specific type of entry (ie: a database scheme, table, whatever)
 */
public class MimirScheme<DATA,ID>
implements Serializable
{
	protected static String extension = ".xsd";

	protected MimirRegistry registry;
	protected MimirDB<DATA,ID> database;
	protected DBMapping mapping;
	protected File schema;

	public MimirScheme(MimirDB<DATA,ID> database, DBMapping mapping)
			throws MimirException
	{
		this.registry = database.getRegistry();
		this.database = database;
		this.mapping = mapping;
		validateMapping();
		locateSchema();
	}

	public MimirRegistry getRegistry(){return registry;}
	public MimirDB<DATA,ID> getDatabase(){return database;}
	public DBMapping getMapping(){return mapping;}
	public File getSchema(){return schema;}

	protected void validateMapping()
			throws SchemeValidationException
	{
		validateMapping(mapping.getId());
		validateMapping(mapping.getDirectory());
		validateMapping(mapping.getSchema());
	}

	protected void validateMapping(String requiredContent)
			throws SchemeValidationException
	{
		if(requiredContent == null) throw new SchemeValidationException(this);
		if(requiredContent.length() == 0) throw new SchemeValidationException(this);
	}

	protected void locateSchema()
			throws MimirException
	{
		File schemeDir = new File(registry.getDatabaseRoot(), mapping.getDirectory());
		URI dirURI = schemeDir.toURI();
		URI schemaURI = null;
		try{
			schemaURI = new URI(mapping.getSchema());
		} catch(URISyntaxException e){
			throw new SchemeNotFoundException(registry, database, mapping.getId(), mapping.getSchema(), e);
		}

		/**
		 * if the schemaURI.getPath() starts with '/'
		 * and doesn't have the same root as the schemeDir
		 * then it is not located inside the database
		 */
		if(schemaURI.isAbsolute() && (!schemaURI.toString().startsWith(dirURI.toString())))
		{
			/**
			 * need to make a local copy of the schema
			 */
//			System.out.println(schemaURI + " does not start with " + dirURI + " so we are going to try to make a local copy");
			URIInputStream uriStream = null;
			try{
				uriStream = new URIInputStream(schemaURI);
				schema = new File(schemeDir, uriStream.getFilename());
				schema.getParentFile().mkdirs();
				FileUtil.copy(uriStream, schema);
				// TODO: use event mechanism to update DBMapping
//System.out.format("Trying to remove %s. Registry has %s\n", mapping.getId(), registry.getConfig().getMappings().size());
//System.out.format("contains? %s\n", registry.containsMapping(mapping));
				registry.removeMapping(mapping);
//				if(!registry.removeMapping(mapping))
//					System.out.format("Removal failed\n");

//System.out.format("Tried to remove %s. Registry has %s\n", mapping.getId(), registry.getConfig().getMappings().size());
				mapping.setSchema(schema.toURI().toString());
				registry.addMapping(mapping);
//System.out.format("Added %s. Registry has %s\n", mapping.getId(), registry.getConfig().getMappings().size());
				validateMapping();
				registry.saveConfig();
			} catch(IOException e){
				throw new SchemeNotFoundException(registry, database, mapping.getId(), schemaURI.toString(), e);
			} catch(MimirException e){
				throw e;
			}
		}else{
			schema = new File(schemaURI);
		}


		// TODO: This isn't going to be correct if the schema isn't in databaseRoot
//		schema = new File(schemeDir, mapping.getSchema());
//		System.out.println("creating file reference from " + mapping.getSchema());
//		schema = new File(mapping.getSchema());

//		System.out.println("file reference = " + schema.getAbsolutePath());

		if(schema.lastModified() == 0)
			throw new SchemeNotFoundException(registry, database, mapping.getId(), schema.getAbsolutePath());
	}

	public MimirEntry<DATA,ID> createEntry(String id, DATA data)
			throws MimirException
	{
		MimirEntry<DATA,ID> entry = new MimirEntry<DATA,ID>(this, id);
		entry.write(data);
		return entry;
	}

	public MimirEntry<DATA,ID> getEntry(String id)
	throws EntryNotFoundException
	{
		MimirEntry<DATA,ID> entry = new MimirEntry<DATA,ID>(this, id);
		if(entry.lastModified() == null)
			throw new EntryNotFoundException(database, this, id, entry.getFile().getAbsolutePath());

		return entry;
	}

	public List<MimirEntry<DATA,ID>> getAllEntries()
	{
		ArrayList<MimirEntry<DATA,ID>> list = new ArrayList<MimirEntry<DATA,ID>>();
		ExtensionFileFilter filter = new ExtensionFileFilter(MimirEntry.extension, false);
		for(File file : schema.getParentFile().listFiles(filter))
			list.add(new MimirEntry<DATA,ID>(this, file));

		return list;
	}

	public List<DATA> getAllData()
			throws MimirException
	{
		return getAllData(null);
	}

	public List<DATA> getAllData(Comparator<DATA> comparator)
			throws MimirException
	{
		ArrayList<DATA> data = new ArrayList<DATA>();
		for(MimirEntry<DATA,ID> entry : getAllEntries())
			data.add(entry.read());

		 if(comparator != null)
		 	Collections.sort(data, comparator);

		return data;
	}

	public Calendar lastModified()
	{
		long lastModified = schema.lastModified();
		if(lastModified == 0)
			return null;

		Calendar cal = Calendar.getInstance();
		cal.setTime(new Date(lastModified));
		return cal;
	}
}
